﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using VCDIFF.Visit;
using VCDIFF.Commands;
using VCDIFF.Tools;

namespace VCDIFF
{
    public class Window
    {
        /*        
        Window organization
            Win_Indicator                            - byte
            [Source segment size]                    - integer
            [Source segment position]                - integer
            The delta encoding of the target window
                Length of the delta encoding         - integer
                The delta encoding
                    Size of the target window        - integer
                    Delta_Indicator                  - byte
                    Length of data for ADDs and RUNs - integer
                    Length of instructions and sizes - integer
                    Length of addresses for COPYs    - integer
                    Data section for ADDs and RUNs   - array of bytes
                    Instructions and sizes section   - array of bytes
                    Addresses section for COPYs      - array of bytes         
         */

        public byte WinIndicator { get; set; }
        public UInt64 SourceSegmentSize { get; set; }
        public UInt64 SourceSegmentPosition { get; set; }
        public UInt64 DeltaEncodingLength { get; set; }
        public UInt64 TargetWindowSize { get; set; }
        public byte DeltaIndicator { get; set; }
        public UInt64 AddAndRunDataLength { get; set; }
        public UInt64 InstructionsAndSizesLength { get; set; }
        public UInt64 CopyAddressesLength { get; set; }
        public byte[] AddAndRunDataSection { get; set; }
        public byte[] InstructionsAndSizesSection { get; set; }
        public byte[] CopyAddressesSection { get; set; }
        public IList<Command> CommandsList { get; set; }

        public Window()
        {
            CommandsList = new List<Command>();
        }

        public void Read(Reader reader)
        {
            reader.Visit(this);
        }

        public void Write(Writer writer)
        {
            writer.Visit(this);
        }

        public void SetWindow(Window window)
        {
            WinIndicator = window.WinIndicator;
            SourceSegmentSize = window.SourceSegmentSize;
            SourceSegmentPosition = window.SourceSegmentPosition;
            DeltaEncodingLength = window.DeltaEncodingLength;
            TargetWindowSize = window.TargetWindowSize;
            DeltaIndicator = window.DeltaIndicator;
            AddAndRunDataLength = window.AddAndRunDataLength;
            InstructionsAndSizesLength = window.InstructionsAndSizesLength;
            CopyAddressesLength = window.CopyAddressesLength;
            AddAndRunDataSection = window.AddAndRunDataSection;
            InstructionsAndSizesSection = window.InstructionsAndSizesSection;
            CopyAddressesSection = window.CopyAddressesSection;
        }

        public void InitializeCommands(IList<Command> commandsList)
        {
            CommandsList = commandsList;

            Writer writer = new Writer();
            foreach (Command command in CommandsList)
                command.Write(writer);

            UInt64 index = 0;
            AddAndRunDataSection = new byte[writer.AddAndRunDataSection.Count];
            foreach (byte b in writer.AddAndRunDataSection)
            {
                AddAndRunDataSection[index] = b;
                index++;
            }
            AddAndRunDataLength = (ulong)AddAndRunDataSection.LongLength;

            index = 0;
            InstructionsAndSizesSection = new byte[writer.InstructionsAndSizesSection.Count];
            foreach (byte b in writer.InstructionsAndSizesSection)
            {
                InstructionsAndSizesSection[index] = b;
                index++;
            }
            InstructionsAndSizesLength = (ulong)InstructionsAndSizesSection.LongLength;

            index = 0;
            CopyAddressesSection = new byte[writer.CopyAddressesSection.Count];
            foreach (byte b in writer.CopyAddressesSection)
            {
                CopyAddressesSection[index] = b;
                index++;
            }
            CopyAddressesLength = (ulong)CopyAddressesSection.LongLength;
        }

        public void InitializeDeltaEncodingLength()
        {
            // + DeltaIndicator
            DeltaEncodingLength = 2;
            DeltaEncodingLength += SFIOInteger.Size(SFIOInteger.Encode(TargetWindowSize)) + 1;
            DeltaEncodingLength += SFIOInteger.Size(SFIOInteger.Encode(AddAndRunDataLength)) + 1;
            DeltaEncodingLength += SFIOInteger.Size(SFIOInteger.Encode(InstructionsAndSizesLength)) + 1;
            DeltaEncodingLength += SFIOInteger.Size(SFIOInteger.Encode(CopyAddressesLength)) + 1;
            DeltaEncodingLength += AddAndRunDataLength;
            DeltaEncodingLength += InstructionsAndSizesLength;
            DeltaEncodingLength += CopyAddressesLength;
        }

        public void InitializeTargetWindowSize()
        {
            Reader reader = new Reader();
            foreach (Command command in CommandsList)
                command.Read(reader);

            TargetWindowSize = reader.TargetWindowSize;
        }

        public byte[] GetWindowData()
        {
            UInt64 index = 0;
            byte[] windowData = new byte[GetWindowSize()];
            windowData[index++] = WinIndicator;

            foreach (byte b in SFIOInteger.Encode(SourceSegmentSize))
                windowData[index++] = b;
            foreach (byte b in SFIOInteger.Encode(SourceSegmentPosition))
                windowData[index++] = b;
            foreach (byte b in SFIOInteger.Encode(DeltaEncodingLength))
                windowData[index++] = b;            
            foreach (byte b in SFIOInteger.Encode(TargetWindowSize))
                windowData[index++] = b;

            windowData[index++] = DeltaIndicator;

            foreach (byte b in SFIOInteger.Encode(AddAndRunDataLength))
                windowData[index++] = b;
            foreach (byte b in SFIOInteger.Encode(InstructionsAndSizesLength))
                windowData[index++] = b;
            foreach (byte b in SFIOInteger.Encode(CopyAddressesLength))
                windowData[index++] = b;
            foreach (byte b in AddAndRunDataSection)
                windowData[index++] = b;
            foreach (byte b in InstructionsAndSizesSection)
                windowData[index++] = b;
            foreach (byte b in CopyAddressesSection)
                windowData[index++] = b;

            return windowData;
        }

        public string GetWindowString()
        {
            StringBuilder windowString = new StringBuilder();
            windowString.Append(WinIndicator);

            foreach (byte b in SFIOInteger.Encode(SourceSegmentSize))
                windowString.Append(b);
            foreach (byte b in SFIOInteger.Encode(SourceSegmentPosition))
                windowString.Append(b);
            foreach (byte b in SFIOInteger.Encode(DeltaEncodingLength))
                windowString.Append(b);
            foreach (byte b in SFIOInteger.Encode(TargetWindowSize))
                windowString.Append(b);

            windowString.Append(DeltaIndicator);

            foreach (byte b in SFIOInteger.Encode(AddAndRunDataLength))
                windowString.Append(b);
            foreach (byte b in SFIOInteger.Encode(InstructionsAndSizesLength))
                windowString.Append(b);
            foreach (byte b in SFIOInteger.Encode(CopyAddressesLength))
                windowString.Append(b);
            foreach (byte b in AddAndRunDataSection)
                windowString.Append(b);
            foreach (byte b in InstructionsAndSizesSection)
                windowString.Append(b);
            foreach (byte b in CopyAddressesSection)
                windowString.Append(b);

            return windowString.ToString();
        }

        /// <summary>
        /// Gets the window size.
        /// </summary>
        /// <param name="data">The data table from which we get the window size.</param>
        /// <returns>The window size.</returns>
        public UInt64 GetWindowSize()
        {
            UInt64 beginning = 0, end = 0;

            beginning = GetWindowHeaderSize();
            end = beginning + SFIOInteger.Size(SFIOInteger.Encode(DeltaEncodingLength));
            end += DeltaEncodingLength;
            end++;

            return end;
        }

        /// <summary>
        /// Returns the size of the window header.
        /// </summary>
        /// <param name="data">ata table from which we want to know the size of the window header.</param>
        /// <returns>Size of the window header.</returns>
        public UInt64 GetWindowHeaderSize()
        {
            UInt64 size = 0;
            // Win_Indicator (byte)
            size++;
            // Source segment size (integer)
            size += SFIOInteger.Size(SFIOInteger.Encode(SourceSegmentSize));
            // next byte
            size++;
            // Source segment position (integer)
            size += SFIOInteger.Size(SFIOInteger.Encode(SourceSegmentPosition));
            // next byte
            size++;

            return size;
        }

        public static Window Read(Stream s)
        {
            using (BinaryReader br = new BinaryReader(s))
            {                
            }

            return new Window();
        }

        public void Write(Stream s)
        {
            using (BinaryWriter bw = new BinaryWriter(s))
            {
            }
        }        
    }
}
